#include "demuxer.h"

#define GOTOEND(condition, msg,flag) \
    if(res) { \
        qDebug() << #msg << " error"; \
        exitFlag = flag;\
        goto end; \
    }

#define ERRMSGGOTO(codition,msg,errFlag) \
    if(codition) {   \
        char errBuf[1024]; \
        av_strerror(errFlag,errBuf,sizeof (errBuf)); \
        qDebug() << errBuf; \
        GOTOEND(condition, msg,errFlag) \
    }


#define ERRMSGRET(codition,msg,errFlag) \
    if(codition) {   \
        char errBuf[1024]; \
        av_strerror(errFlag,errBuf,sizeof (errBuf)); \
        qDebug() << errBuf; \
        return errFlag; \
    }

Demuxer::Demuxer()
{

}

int Demuxer::demuxer(const char *videoPath, YUVFileSpec &yuvFileSpec, PCMFileSpec &pcmFileSpec)
{

    int res = 0, exitFlag = 0;
    _yuvFileSpec = &yuvFileSpec;
    _pcmFileSpec = &pcmFileSpec;

    res = avformat_open_input(&_fmtContext,videoPath,nullptr,nullptr);
    GOTOEND(res,avformat_open_inputm, -1);

    res = avformat_find_stream_info(_fmtContext,nullptr);
    ERRMSGGOTO(res < 0,avformat_find_stream_info,res);

    av_dump_format(_fmtContext,0,videoPath,0);
    fflush(stderr);

    res = initVideo();

    GOTOEND(res,initVideo,res);

    res = initAudio();

    GOTOEND(res,initAudio,res);

    _packet = av_packet_alloc();
    _packet->data = nullptr;
    _packet->size = 0;
    GOTOEND(_packet == nullptr,av_packet_alloc,-1);


    _frame = av_frame_alloc();

    GOTOEND(_frame == nullptr,av_frame_alloc,-1);


    while (av_read_frame(_fmtContext,_packet) == 0) {

        if(_packet->stream_index == _vStreamIdx) {

           res = decode(_vCodecContext,_packet,_frame,&Demuxer::writeVideo);

        }else if(_packet->stream_index == _aStreamIdx) {

           res = decode(_aCodecContext,_packet,_frame,&Demuxer::writeAudio);

        }

        av_packet_unref(_packet);

        if(res < 0) {

            goto end;
        }

    }


    decode(_vCodecContext,nullptr,_frame,&Demuxer::writeVideo);
    decode(_aCodecContext,nullptr,_frame,&Demuxer::writeAudio);

end:

    av_packet_free(&_packet);
    av_frame_free(&_frame);
    avcodec_free_context(&_aCodecContext);
    avcodec_free_context(&_vCodecContext);

    if(_vOutFile != nullptr) {

        _vOutFile->close();
    }

    if(_aOutFile != nullptr) {

        _aOutFile->close();
    }



    return exitFlag;
}


int Demuxer::init(enum AVMediaType type,int* streamIdx, AVCodecContext** codecContext)
{

    int res = av_find_best_stream(_fmtContext,type,-1,-1,nullptr,0);

    ERRMSGRET(res < 0,av_find_best_stream,res);

    *streamIdx = res;

    AVStream* stream = _fmtContext->streams[*streamIdx];

    if(stream == nullptr) {

        qDebug() << "not find" << *streamIdx << " stream";
        return -1;
    }

    AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);

    if(codec == nullptr) {

        qDebug() << "avcodec_find_decoder not find codec";
        return -1;
    }

   *codecContext = avcodec_alloc_context3(codec);

   if(_vCodecContext == nullptr) {

       qDebug() << "avcodec_alloc_context3 error";
       return -1;
   }


   res = avcodec_parameters_to_context(*codecContext,stream->codecpar);

   if(res < 0) {

       avcodec_free_context(codecContext);
       qDebug() << "avcodec_parameters_to_context error";
       return res;
   }

   res = avcodec_open2(*codecContext,codec,nullptr);

   if(res < 0) {

       avcodec_free_context(codecContext);
       qDebug() << "avcodec_open2 error";
       return res;
   }

   return 0;
}

int Demuxer::initVideo()
{

    if(init(AVMEDIA_TYPE_VIDEO,&_vStreamIdx,&_vCodecContext) < 0) {

        qDebug() << "init error";
        return -1;
    }

    _vOutFile = new QFile(_yuvFileSpec->getFilePath());

    if(!_vOutFile->open(QFile::WriteOnly)) {

        qDebug() << _yuvFileSpec->getFilePath() << "open fail";
        return -1;
    }

    _yuvFileSpec->setWidth(_vCodecContext->width);
    _yuvFileSpec->setHeight(_vCodecContext->height);
    _yuvFileSpec->setFormat(_vCodecContext->pix_fmt);
    _yuvFileSpec->setFPS(_vCodecContext->framerate.den);

    _yuvFileSpec->reCalImageSize();

    return 0;
}

int Demuxer::initAudio()
{

    int res = init(AVMEDIA_TYPE_AUDIO,&_aStreamIdx,&_aCodecContext);

    if(res < 0) {

        qDebug() << "audio init error";
        return -1;
    }

    _aOutFile = new QFile(_pcmFileSpec->getFilePath());

    if(!_aOutFile->open(QFile::WriteOnly)) {

        qDebug() << _pcmFileSpec->getFilePath() << "open fail";
        return -1;
    }

    _pcmFileSpec->setSampleRate(_aCodecContext->sample_rate);
    _pcmFileSpec->setChannelLayout(_aCodecContext->channel_layout);
    _pcmFileSpec->setSampleFormat(_aCodecContext->sample_fmt);

    _pcmFileSpec->calFrameSize();

    return 0;
}

int Demuxer::decode(AVCodecContext *codecContext, AVPacket *packet, AVFrame *frame, void (Demuxer::* func)())
{


    int res = avcodec_send_packet(codecContext,packet);

    if(res == AVERROR(EAGAIN) || res == AVERROR_EOF) {

        return 0;
    }else if(res < 0){

        qDebug() << "avcodec_send_packet error";
        return res;
    }

    while (true) {

        res = avcodec_receive_frame(codecContext,frame);

        if(res == AVERROR(EAGAIN) || res == AVERROR_EOF) {

            return 0;
        }else if(res < 0) {

            qDebug() << "avcodec_receive_frame fail";
            return res;
        }


        (this->*func)();

    }

}

void Demuxer::writeVideo()
{

    int height = _vCodecContext->height;
    int width = _yuvFileSpec->getWidth();
    int yStep = _frame->linesize[0];
    int uStep = _frame->linesize[1];
    int vStep = _frame->linesize[2];

    for(int i = 0; i < height;i++) {


        _vOutFile->write((char*)(_frame->data[0] + yStep*i),width);
    }

    for(int i = 0; i < (height >> 1);i++) {

        _vOutFile->write((char*)(_frame->data[1] + uStep*i),width >> 1);
    }

    for(int i = 0; i < (height >> 1);i++) {

        _vOutFile->write((char*)(_frame->data[2] + vStep*i),width >> 1);
    }

}

void Demuxer::writeAudio()
{



}
















